package com.stephen.jsbridge.xwalkbridge;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Looper;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.webkit.WebSettings;
import android.webkit.WebView;

import com.stephen.jsbridge.jsbridge.BridgeHandler;
import com.stephen.jsbridge.jsbridge.CallBackFunction;
import com.stephen.jsbridge.jsbridge.OnWebViewListener;
import com.stephen.jsbridge.jsbridge.WebViewJavascriptBridge;

import org.xwalk.core.XWalkSettings;
import org.xwalk.core.XWalkView;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by stephen on 10/24/17.
 */

public class XWalkWebView extends XWalkView implements WebViewJavascriptBridge {
    public static final int req_SystemCamera = 997,req_ActivityCamera = 998,req_ActivityPhoto = 999;
    public static final String toLoadJs = "www/WebViewJavascriptBridge.js";
    private long uniqueId = 0;
    private XWalkWebViewClient webViewClient = null;
    private XWalkWebChromeClient webChromeClient = null;
    private Map<String, CallBackFunction> responseCallbacks = new HashMap<String, CallBackFunction>();
    private Map<String, BridgeHandler> messageHandlers = new HashMap<String, BridgeHandler>();
    private BridgeHandler defaultHandler = new DefaultHandler();
    private List<Message> startupMessage = new ArrayList<Message>();

    public XWalkWebView(Context context) {
        super(context);
    }

    public XWalkWebView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public void init(Activity activity, boolean notSetClient, OnWebViewListener onWebViewListener){
        this.setInitialScale(25);//default 0
        this.setVerticalScrollBarEnabled(false);
        this.setHorizontalScrollBarEnabled(false);
        setWebViewSetting(getSettings());
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) WebView.setWebContentsDebuggingEnabled(true);
        webViewClient = new XWalkWebViewClient(activity,this,onWebViewListener);
        if(!notSetClient)setUIClient(webViewClient);
        webChromeClient = new XWalkWebChromeClient(activity,this,onWebViewListener);
        if(!notSetClient)setResourceClient(webChromeClient);
    }

    private void setWebViewSetting(XWalkSettings settings){
        settings.setJavaScriptEnabled(true);
        settings.setJavaScriptCanOpenWindowsAutomatically(true);
        //让加载进来的页面自适应手机屏幕分辨率居中显示
        //settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
        settings.setUseWideViewPort(true);
        settings.setLoadWithOverviewMode(true);

        // Set the nav dump for HTC 2.x devices (disabling for ICS, deprecated entirely for Jellybean 4.2)
        try {
            Method gingerbread_getMethod =  WebSettings.class.getMethod("setNavDump", new Class[] { boolean.class });

            String manufacturer = Build.MANUFACTURER;
            System.out.println("CordovaWebView is running on device made by: " + manufacturer);
            if(Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB && Build.MANUFACTURER.contains("HTC")) {
                gingerbread_getMethod.invoke(settings, true);
            }
        } catch (NoSuchMethodException e) {
            System.out.println("We are on a modern version of Android, we will deprecate HTC 2.3 devices in 2.8");
        } catch (IllegalArgumentException e) {
            System.out.println("Doing the NavDump failed with bad arguments");
        } catch (IllegalAccessException e) {
            System.out.println("This should never happen: IllegalAccessException means this isn't Android anymore");
        } catch (InvocationTargetException e) {
            System.out.println("This should never happen: InvocationTargetException means this isn't Android anymore.");
        }

        // Jellybean rightfully tried to lock this down. Too bad they didn't give us a whitelist while we do this
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            settings.setAllowUniversalAccessFromFileURLs(true);
        }else{
			/*try {
				Class<?> clazz = settings.getClass();
				Method method = clazz.getMethod("setAllowUniversalAccessFromFileURLs", boolean.class);
				if(null != method)method.invoke(settings, true);
			} catch (NoSuchMethodException e) {
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}*/
        }

        //We don't save any form data in the application
        settings.setSaveFormData(false);
        //settings.setSavePassword(false);

        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)settings.setMediaPlaybackRequiresUserGesture(false);
        // Enable database
        // We keep this disabled because we use or shim to get around DOM_EXCEPTION_ERROR_16
        String databasePath = getContext().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
        settings.setDatabaseEnabled(true);
        //settings.setDatabasePath(databasePath);

        //settings.setGeolocationDatabasePath(databasePath);

        // Enable DOM storage
        settings.setDomStorageEnabled(true);

        // Enable built-in geolocation
        //settings.setGeolocationEnabled(true);

        settings.setAllowContentAccess(true);
        settings.setAllowFileAccessFromFileURLs(true);
        settings.setAllowUniversalAccessFromFileURLs(true);

        // Enable AppCache
        // Fix for CB-2282
        /*settings.setAppCacheMaxSize(5 * 1048576);
        settings.setAppCachePath(databasePath);
        settings.setAppCacheEnabled(true);*/
        settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
    }

    void handlerReturnData(String url) {
        String functionName = BridgeUtil.getFunctionFromReturnUrl(url);
        CallBackFunction f = responseCallbacks.get(functionName);
        String data = BridgeUtil.getDataFromReturnUrl(url);
        if (f != null) {
            f.onCallBack(data);
            responseCallbacks.remove(functionName);
            return;
        }
    }

    @Override
    public void send(String data) {
        send(data, null);
    }

    @Override
    public void send(String data, CallBackFunction responseCallback) {
        doSend(null, data, responseCallback);
    }

    //default handler,handle messages send by js without assigned handler name,if js message has handler name, it will be handled by named handlers registered by native
    public void setDefaultHandler(BridgeHandler handler) {
        this.defaultHandler = handler;
    }

    private void doSend(String handlerName, String data, CallBackFunction responseCallback) {
        Message m = new Message();
        if (!TextUtils.isEmpty(data)) {
            m.setData(data);
        }
        if (responseCallback != null) {
            String callbackStr = String.format(BridgeUtil.CALLBACK_ID_FORMAT, ++uniqueId + (BridgeUtil.UNDERLINE_STR + SystemClock.currentThreadTimeMillis()));
            responseCallbacks.put(callbackStr, responseCallback);
            m.setCallbackId(callbackStr);
        }
        if (!TextUtils.isEmpty(handlerName)) {
            m.setHandlerName(handlerName);
        }
        queueMessage(m);
    }

    private void queueMessage(Message m) {
        if(startupMessage != null){
            startupMessage.add(m);
        }else{
            dispatchMessage(m);
        }
    }

    void dispatchMessage(Message m) {
        String messageJson = m.toJson();
        //escape special characters for json string
        messageJson = messageJson.replaceAll("(\\\\)([^utrn])", "\\\\\\\\$1$2");
        messageJson = messageJson.replaceAll("(?<=[^\\\\])(\")", "\\\\\"");
        String javascriptCommand = String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, messageJson);
        if(Thread.currentThread() == Looper.getMainLooper().getThread()){
            this.loadUrl(javascriptCommand);
        }else{
            System.out.println("===>有Js消息未交互成功,因为线程不是同一个,消息内容:"+messageJson);
        }
    }

    void flushMessageQueue() {
        if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
            loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() {

                @Override
                public void onCallBack(String data) {
                    // deserializeMessage
                    List<Message> list = null;
                    try {
                        list = Message.toArrayList(data);
                    } catch (Exception e) {
                        e.printStackTrace();
                        return;
                    }
                    if (list == null || list.size() == 0) {
                        return;
                    }
                    for (int i = 0; i < list.size(); i++) {
                        Message m = list.get(i);
                        String responseId = m.getResponseId();
                        // 是否是response
                        if (!TextUtils.isEmpty(responseId)) {
                            CallBackFunction function = responseCallbacks.get(responseId);
                            String responseData = m.getResponseData();
                            function.onCallBack(responseData);
                            responseCallbacks.remove(responseId);
                        } else {
                            CallBackFunction responseFunction = null;
                            // if had callbackId
                            final String callbackId = m.getCallbackId();
                            if (!TextUtils.isEmpty(callbackId)) {
                                responseFunction = new CallBackFunction() {
                                    @Override
                                    public void onCallBack(String data) {
                                        Message responseMsg = new Message();
                                        responseMsg.setResponseId(callbackId);
                                        responseMsg.setResponseData(data);
                                        queueMessage(responseMsg);
                                    }
                                };
                            } else {
                                responseFunction = new CallBackFunction() {
                                    @Override
                                    public void onCallBack(String data) {
                                        // do nothing
                                    }
                                };
                            }
                            BridgeHandler handler;
                            if (!TextUtils.isEmpty(m.getHandlerName())) {
                                handler = messageHandlers.get(m.getHandlerName());
                            } else {
                                handler = defaultHandler;
                            }
                            if(handler != null)handler.handler(m.getData(), responseFunction);
                        }
                    }
                }
            });
        }
    }

    public void loadUrl(String jsUrl, CallBackFunction returnCallback) {
        this.loadUrl(jsUrl);
        responseCallbacks.put(BridgeUtil.parseFunctionName(jsUrl), returnCallback);
    }

    //register handler,so that javascript can call it
    public void registerHandler(String handlerName, BridgeHandler handler) {
        if(handler != null)messageHandlers.put(handlerName, handler);
    }

    //call javascript registered handler call javascript registered handler
    public void callHandler(String handlerName, String data, CallBackFunction callBack) {
        doSend(handlerName, data, callBack);
    }

    public void callOnActivityResult(int requestCode, int resultCode, Intent intent){
        switch(requestCode){
            case req_SystemCamera:
            case req_ActivityCamera:
            case req_ActivityPhoto:
                if(null != webViewClient)webViewClient.callOnActivityResult(requestCode, resultCode, intent);
                break;
        }//end of switch
    }

    public XWalkWebViewClient getWebViewClient() {
        return webViewClient;
    }

    public XWalkWebChromeClient getWebChromeClient() {
        return webChromeClient;
    }

    public List<Message> getStartupMessage() {
        return startupMessage;
    }

    public void setStartupMessage(List<Message> startupMessage) {
        this.startupMessage = startupMessage;
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if(KeyEvent.KEYCODE_BACK == event.getKeyCode())return false;
        return super.dispatchKeyEvent(event);
    }
}
